package work.yixin.redis.config;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
 * RedisRepository切面
 * @author yunshang
 */
@Component
@Aspect
public class RedisRepositoryAspect {
	/**
	 * 存储当前Jedis对象
	 */
	public static final ThreadLocal<Jedis> LocalRedisJedis = new ThreadLocal<>();
	/**
	 * 存储嵌套数据库ID对象
	 */
	public static final ThreadLocal<JedisStack> LocalRedisJedisVector = new ThreadLocal<>();
	
	@Autowired
	private JedisPool jedisPool;
	
	/**
	 * 定义切入点：拦截方法上有RedisRepository注解的所有方法，拦截类上有RedisRepository注解的类下的所有方法
	 * 加在方法上的注解优先级比加在类上的优先级更高，如果同时在类上和类中的方法上同时加上注解，那么会优先使用方法上的注解参数
	 */
	@Pointcut("@within(work.yixin.redis.config.RedisRepository) || @annotation(work.yixin.redis.config.RedisRepository)")
	public void redisRepository() {
		
	}
	
	/**
	 * 前置通知：在目标方法执行前调用
	 */
	@Before("redisRepository()")
	public void begin(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        RedisRepository redisRepository = method.getAnnotation(RedisRepository.class);
		int db = 0;
		if (redisRepository!=null&&redisRepository.value()>=0) {
			db = redisRepository.value();
		}
		if (RedisRepositoryAspect.LocalRedisJedis.get()!=null) {
			Jedis jedis = RedisRepositoryAspect.LocalRedisJedis.get();
			JedisStack stack = RedisRepositoryAspect.LocalRedisJedisVector.get();
			if (stack==null) {
				stack = new JedisStack();
				RedisRepositoryAspect.LocalRedisJedisVector.set(stack);
			}
			stack.push(jedis.getDB());
			jedis.select(db);
		}else{
			Jedis jedis = jedisPool.getResource();
			jedis.select(db);
			RedisRepositoryAspect.LocalRedisJedis.set(jedis);
		}
		
	}
	/**
	 * 环绕通知：灵活自由的在目标方法中切入代码
	 */
	@Around("redisRepository()")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
		return joinPoint.proceed();
	}
	/**
	 * 后置通知：在目标方法执行后调用，若目标方法出现异常，则不执行
	 */
	@AfterReturning(value = "redisRepository()", returning = "result")
	public void afterReturning(JoinPoint joinPoint, Object result) {

	}

	/**
	 * 异常通知：目标方法抛出异常时执行
	 */
	@AfterThrowing(value = "redisRepository()", throwing = "throwable")
	public void afterThrowing(JoinPoint joinPoint, Throwable throwable) {

	}

	/**
	 * 后置/最终通知：无论目标方法在执行过程中出现一场都会在它之后调用
	 */
	@After("redisRepository()")
	public void after() {
		Jedis jedis = RedisRepositoryAspect.LocalRedisJedis.get();
		JedisStack stack = RedisRepositoryAspect.LocalRedisJedisVector.get();
		if (stack!=null) {
			if (stack.size()>0) {
				int db = stack.pop();
				jedis.select(db);
			}
			if (stack.size()==0) {
				RedisRepositoryAspect.LocalRedisJedisVector.remove();
			}
		}else {
			RedisRepositoryAspect.LocalRedisJedis.remove();
			if (jedis!=null&&jedis.isConnected()) {
				jedis.close();
			}
		}
	}
	
	class JedisStack {

		int[] data = new int[5];
		int i = 0; // 数组下标

		// 入栈
		public void push(int db) {
			data[i] = db;
			i++;
		}

		// 出栈
		public int pop() {
			if (i > 0) {
				int db = data[i-1];
				data[i-1] = 0;
				i--;
				return db;
			}
			return 0;
		}
		
		// 栈中元素数量
		public int size() {
			return i;
		}
	}
}
